#include "productlistmodel.h"

#include <QApplication>
#include <QDebug>
#include <QDirIterator>
#include <QFile>
#include <QtAlgorithms>
#include <QTextStream>
#include <QTimer>

#include "catalog_constants.h"
#include "purchasableitem.h"

//to be able to transfer data this type with Qt::QueuedConnection
Q_DECLARE_METATYPE(IAPClient::ProductDataList)

ProductListModel::ProductListModel(QObject *parent) :
    QAbstractListModel(parent),
    m_modelCommitRefCounter(0),
    m_requestId(KErrNotFound),
    m_isApplicationBusy(false),
    m_client(NULL)
{
    qRegisterMetaType<IAPClient::ProductDataList>("IAPClient::ProductDataList");
    qRegisterMetaType<IAPClient::UserAndDeviceDataHash>("IAPClient::ProductDataHash");

    QHash<int, QByteArray> roles;
    roles[PurchasableItemBase::ProductIdRole] = "iap_productid";
    roles[PurchasableItemBase::DataReqResultRole] = "iap_reqresult";
    roles[PurchasableItemBase::TitleRole] = "iap_title";
    roles[PurchasableItemBase::ShortDescriptionRole] = "iap_shortDescr";
    roles[PurchasableItemBase::LongDescriptionRole] = "iap_longDescr";
    roles[PurchasableItemBase::PriceRole] = "iap_price";
    roles[PurchasableItemBase::DrmProtectionRole] = "iap_isDrm";
    roles[PurchasableItemBase::IsMetadataKnown] = "iap_isDataKnown";
    roles[PurchasableItemBase::IsBusy] = "iap_isBusy";
    roles[PurchasableItemBase::IsActivated] = "iap_isActivated";
    setRoleNames(roles);
    connect(this, SIGNAL(modelUpdated()), parent, SLOT(modelUpdated()));

    m_isApplicationBusy = true;
}

ProductListModel::~ProductListModel()
{
    delete m_client;
}

QVariant ProductListModel::data(const QModelIndex &index,
                                            int role) const
{
    const PurchasableItemBase *record = m_data[index.row()];
    return record->metadata(role);
}

int ProductListModel::rowCount(const QModelIndex& /*parent*/) const
{
    return m_data.count();
}

bool ProductListModel::setData(const QModelIndex &index,
                                           const QVariant &value,
                                           int role)
{
    if (role < PurchasableItemBase::ProductIdRole || index.row() == m_data.count())
        return false;

    PurchasableItemBase *record = const_cast<PurchasableItemBase*>(m_data[index.row()]);
    return record->setMetadata(value, role);
}


void ProductListModel::readDRMCatalog()
{
    QString resources_path = QApplication::applicationDirPath();
    resources_path.append(KProtectedCatalog);

    QString icons_path = QApplication::applicationDirPath();
    icons_path.append(KProductIcon);

    QDirIterator dirit(resources_path, QDir::Dirs, QDirIterator::NoIteratorFlags);

    //NOTE from profiling data, the whole cycle to complete takes 0.3774 seconds
    while (dirit.hasNext()){
        QString dir = dirit.next();
        if (dir.contains(KResourcePattern, Qt::CaseInsensitive)){
            // breakdown the !:/private/<SID>/drm/data/resourceid_<productID>/
            // extract the product to which this file belongs
            QString product;
            QStringList elem = dir.split("_", QString::SkipEmptyParts);
            elem=elem[1].split("/", QString::SkipEmptyParts);
            product = elem[0].trimmed();
            if (product.isEmpty())
                continue;

            PurchasableItem *record = new PurchasableItem(*this, product, dir);
            beginInsertRows(QModelIndex(), rowCount(), rowCount());
            m_data << record;
            endInsertRows();
        }
    }

    //NOTE from profiling data, instantiating IAP client takes 0.5223 seconds
    // that is why it should be created in a worker thread
    // another point is the following : we cannot create child object for parent in separate thread
    // and even worse, IAPclient implements a dialog thus it must be created in GUI, main thread
    // Taking all above into account we decided to use timer to execute 'initIAP' slot in future
    QTimer::singleShot(10, this, SLOT(initIAP()));
}

void ProductListModel::initIAP()
{
    if(m_client)
        return; //do it only once

    m_client = new IAPClient(this);
    connect(m_client, SIGNAL(
                restorableProductsReceived (int, QString, IAPClient::ProductDataList)),
            this, SLOT(
                restorableProductsReceived (int, QString, IAPClient::ProductDataList)),
            Qt::QueuedConnection);

    connect(m_client, SIGNAL(
                userAndDeviceDataReceived(int, QString,
                                          IAPClient::UserAndDeviceDataHash)),
            this, SLOT(
                userAndDeviceDataReceived(int, QString,
                                          IAPClient::UserAndDeviceDataHash)),
            Qt::QueuedConnection);

    foreach(PurchasableItemBase* item, m_data){
        item->initProductData();
    }

    m_isApplicationBusy = false;
    emit applicationBusyChanged();
}

void ProductListModel::beginModelUpdate()
{
    if (!m_modelCommitRefCounter)
        beginResetModel();
    m_modelCommitRefCounter++;
}

void ProductListModel::commitModelUpdate()
{
    m_modelCommitRefCounter--;
    if (m_modelCommitRefCounter < 0)
        m_modelCommitRefCounter = 0;

    if (!m_modelCommitRefCounter){
        endResetModel();

        // notify parent model was updated
        if (allowModelUpdateNotifications()){
            emit modelUpdated();
        }
    }
}

IAPClient &ProductListModel::client()
{
    return *m_client;
}

void ProductListModel::buy(const QModelIndex &index)
{
    const_cast<PurchasableItemBase*>(record(index))->purchase();
}

bool ProductListModel::allowModelUpdateNotifications()
{
    bool result = true;
    foreach (PurchasableItemBase* item, m_data)
    {
         result &= item->isMetaDataKnown();
    }
    return result;
}

void ProductListModel::restoreProducts()
{
    if (m_requestId >= 0){
        qDebug() << "Catalog is busy. you can set only one request at time";
        return;
    }

    qDebug(__PRETTY_FUNCTION__);

    m_requestId = m_client->getRestorableProducts();
    if (m_requestId < 0)
        return; // failure hapened

    beginResetModel();
    PurchasableItemBase* item;

    foreach (item, m_data){
        item->setMetadata(QVariant(true), PurchasableItemBase::IsBusy);
    }
    endResetModel();
}

void ProductListModel::getUserInfo()
{
    if (m_requestId >= 0){
        qDebug() << "Catalog is busy. you can set only one request at time";
        return;
    }

    qDebug(__PRETTY_FUNCTION__);

    m_requestId = m_client->getUserAndDeviceId();
    if (m_requestId < 0)
        return; // failure hapened

    m_isApplicationBusy = true;
    emit applicationBusyChanged();
}


void ProductListModel::purchaseFlowFinished(bool status)
{
    emit purchaseCompleted(status);
}

PurchasableItemBase *ProductListModel::record(int index) const
{
    if (index == m_data.count())
        return NULL;

    return (m_data[index]);
}

PurchasableItemBase *ProductListModel::record(const QModelIndex &index) const
{
    if (index.row() == m_data.count())
        return NULL;

    return (m_data[index.row()]);
}

bool ProductListModel::isApplicationBusy() const
{
    return m_isApplicationBusy;
}

void ProductListModel::userAndDeviceDataReceived(int requestId, QString status,
                                            IAPClient::UserAndDeviceDataHash response)
{
    qDebug() << "userAndDeviceDataReceived : " << status;
}

void ProductListModel::restorableProductsReceived( int requestId, QString status,
                                             IAPClient::ProductDataList items )
{
    qDebug() << "restorableProductsReceived with status: " << status;
}

void ProductListModel::resetPurchases()
{
    QString dirName = PurchasableItemBase::getTicketDir();
    QDir dir(dirName);

    if(!dir.exists(dirName))
        return;

    bool result;
    foreach(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot |
                                              QDir::System |
                                              QDir::Hidden  |
                                              QDir::AllDirs |
                                              QDir::Files,
                                              QDir::DirsFirst))
    {
        result = QFile::remove(info.absoluteFilePath());
        if (!result)
        {
            break;
        }
    }
    dir.rmdir(dirName);
    beginResetModel();
    PurchasableItemBase* item;

    foreach (item, m_data){
        item->setMetadata(QVariant(false), PurchasableItemBase::IsActivated);
    }
    endResetModel();
}
